<?php
/***********************************************************************
 * @package     IvanEngine
 * @subpackage  Framework
 * @author      Ivan <ivanzx@msn.com>
 * @created     2010-06-01
 ***********************************************************************/
if(defined("IVANENGINE_FRAMEWORK_USER"))
    die("Redefinition macro in :".__FILE__."(".__LINE__.")");
define("IVANENGINE_FRAMEWORK_USER", true);

require_once (dirname(__FILE__).'/Common.php');
require_once (M_CORE_PATH.'STL/NamespaceArray.php');

class CUser extends CObject
{
    const OT_AutoStart              = 'OT_AutoStart';
    const OT_Culture                = 'OT_Culture';
    const OT_CultureDefault         = 'OT_CultureDefault';
    const OT_UseFlash               = 'OT_UseFlash';
    const OT_Timeout                = 'OT_Timeout';
    
    // Storage Namespace
    const SN_Attribute              = 'User::Attribute';
    const SN_Culture                = 'User::Culture';
    const SN_Flash                  = 'User::Flash';
    const SN_FlashRemove            = 'User::FlashRemove';
    const SN_LastRequest            = 'User::LastRequest';
    const SN_Credential             = 'User::Credential';
    const SN_Auth                   = 'User::Auth';

    protected
        $m_options                  = array(),
        $m_storage                  = null,
        $m_attributes               = null,
        
        $m_lastRequest              = null,
        $m_credentials              = null,
        $m_authenticated            = null,
        $m_timedout                 = false;
        
    public function __construct($options = array())
    {
        parent::__construct();
        
        $this->Initialize(CContext::Get()->GetStorage(), $options);
        
        if ($this->m_options[self::OT_AutoStart])
        {
            register_shutdown_function(array($this, 'Shutdown'));
        }
    }

    public function __destruct()
    {
        parent::__destruct();
    }
    
    public function Initialize($storage, $options = array())
    {
        $this->m_storage    = $storage;
        $this->m_options = array_merge(array(
            self::OT_AutoStart      => true,
            self::OT_Culture        => null,
            self::OT_CultureDefault=> 'en',
            self::OT_UseFlash      => true,
        ), $options);
        
        $this->m_attributes = new CNamespaceArray();
        
        // read attributes from storage
        $attributes         = $this->m_storage->Read(self::SN_Attribute);
        if (is_array($attributes))
        {
            foreach ($attributes as $namespace => $values)
            {
                $this->m_attributes->Add($values, $namespace);
            }
        }
        
        // flag current flash to be removed at shutdown
        if ($this->m_options[self::OT_UseFlash] && $names = $this->m_attributes->GetKeys(self::SN_Flash))
        {
            foreach ($names as $name)
            {
                $this->m_attributes->Set($name, true, self::SN_FlashRemove);
            }
        }
        
        {
            if (!array_key_exists(self::OT_Timeout, $this->m_options))
            {
                $this->m_options[self::OT_Timeout] = 1800;
            }

            // force the max lifetime for session garbage collector to be greater than timeout
            if (ini_get('session.gc_maxlifetime') < $this->m_options[self::OT_Timeout])
            {
                ini_set('session.gc_maxlifetime', $this->m_options[self::OT_Timeout]);
            }

            // read data from storage
            $this->m_authenticated = $this->m_storage->Read(self::SN_Auth);
            $this->m_credentials   = $this->m_storage->Read(self::SN_Credential);
            $this->m_lastRequest   = $this->m_storage->Read(self::SN_LastRequest);

            if (is_null($this->m_authenticated))
            {
                $this->m_authenticated = false;
                $this->m_credentials   = array();
            }
            else
            {
                // Automatic logout logged in user if no request within timeout parameter seconds
                $timeout = $this->m_options[self::OT_Timeout];
                if (false !== $timeout && !is_null($this->m_lastRequest) && time() - $this->m_lastRequest >= $timeout)
                {
                    $this->SetTimedOut();
                    $this->SetAuthenticated(false);
                }
            }

            $this->m_lastRequest = time();
        }
    }
    
    public function GetOptions()
    {
        return $this->m_options;
    }
    
    public function SetFlash($name, $value, $persist = true)
    {
        if (!$this->m_options[self::OT_UseFlash])
        {
            return;
        }

        $this->SetAttribute($name, $value, self::SN_Flash);

        if ($persist)
        {
            // clear removal flag
            $this->m_attributes->Remove($name, null, self::SN_FlashRemove);
        }
        else
        {
            $this->SetAttribute($name, true, self::SN_FlashRemove);
        }
    }
    
    public function GetFlash($name, $default = null)
    {
        if (!$this->m_options[self::OT_UseFlash])
        {
            return $default;
        }

        return $this->GetAttribute($name, $default, self::SN_Flash);
    }
    
    public function HasFlash($name)
    {
        if (!$this->m_options[self::OT_UseFlash])
        {
            return false;
        }

        return $this->HasAttribute($name, self::SN_Flash);
    }
    
    public function GetAttributes()
    {
        return $this->m_attributes;
    }

    public function GetAttribute($key, $default = null, $namespace = null)
    {
        return $this->m_attributes->Get($key, $default, $namespace);
    }

    public function HasAttribute($key, $namespace = null)
    {
        return $this->m_attributes->Has($key, $namespace);
    }

    public function SetAttribute($key, $value, $namespace = null)
    {
        return $this->m_attributes->Set($key, $value, $namespace);
    }
    
        /**
    * Clears all credentials.
    *
    */
    public function ClearCredentials()
    {
        $this->m_credentials = null;
        $this->m_credentials = array();
    }

    /**
    * returns an array containing the credentials
    */
    public function GetCredentials()
    {
        return $this->m_credentials;
    }

    /**
    * Removes a credential.
    *
    * @param  mixed $credential credential
    */
    public function RemoveCredential($credential)
    {
        if ($this->HasCredential($credential))
        {
            foreach ($this->m_credentials as $key => $value)
            {
                if ($credential == $value)
                {
                    unset($this->m_credentials[$key]);
                    $this->m_storage->Regenerate(false);
                    return;
                }
            }
        }
    }

    /**
    * Adds a credential.
    *
    * @param mixed $credential
    */
    public function AddCredential($credential)
    {
        $this->AddCredentials(func_get_args());
    }

    /**
    * Adds several credential at once.
    *
    * @param  mixed array or list of credentials
    */
    public function AddCredentials()
    {
        if (func_num_args() == 0) return;

        // Add all credentials
        $credentials = (is_array(func_get_arg(0))) ? func_get_arg(0) : func_get_args();

        $added = false;
        foreach ($credentials as $aCredential)
        {
            if (!in_array($aCredential, $this->m_credentials))
            {
                $added = true;
                $this->m_credentials[] = $aCredential;
            }
        }

        if ($added)
        {
            $this->m_storage->m_regenerate(false);
        }
    }

    /**
    * Returns true if user has credential.
    *
    * @param  mixed $credentials
    * @param  bool  $useAnd       specify the mode, either AND or OR
    * @return bool
    *
    * @author Olivier Verdier <Olivier.Verdier@free.fr>
    */
    public function HasCredential($credentials, $useAnd = true)
    {
        if (!is_array($credentials))
        {
            return in_array($credentials, $this->m_credentials);
        }

        // now we assume that $credentials is an array
        $test = false;

        foreach ($credentials as $credential)
        {
            // recursively check the credential with a switched AND/OR mode
            $test = $this->HasCredential($credential, $useAnd ? false : true);

            if ($useAnd)
            {
                $test = $test ? false : true;
            }

            if ($test) // either passed one in OR mode or failed one in AND mode
            {
                break; // the matter is settled
            }
        }

        if ($useAnd) // in AND mode we succeed if $test is false
        {
            $test = $test ? false : true;
        }
        return $test;
    }

    /**
    * Returns true if user is authenticated.
    *
    * @return boolean
    */
    public function IsAuthenticated()
    {
        return $this->m_authenticated;
    }

    /**
    * Sets authentication for user.
    *
    * @param  bool $authenticated
    */
    public function SetAuthenticated($authenticated)
    {
        if ((bool) $authenticated !== $this->m_authenticated)
        {
            if ($authenticated === true)
            {
                $this->m_authenticated = true;
            }
            else
            {
                $this->m_authenticated = false;
                $this->ClearCredentials();
            }

            $this->m_storage->Regenerate(false);
        }
    }

    public function SetTimedOut()
    {
        $this->m_timedout = true;
    }

    public function IsTimedOut()
    {
        return $this->m_timedout;
    }

    /**
    * Returns the timestamp of the last user request.
    *
    * @param  int
    */
    public function GetLastRequestTime()
    {
        return $this->LastRequest;
    }
    
    public function Shutdown()
    {
        // write the last request time to the storage
        $this->m_storage->Write(self::SN_LastRequest,   $this->m_lastRequest);
        $this->m_storage->Write(self::SN_Auth,          $this->m_authenticated);
        $this->m_storage->Write(self::SN_Credential,    $this->m_credentials);
    
        // remove flash that are tagged to be removed
        if ($this->m_options[self::OT_UseFlash] && $names = $this->m_attributes->GetKeys(self::SN_FlashRemove))
        {
            foreach ($names as $name)
            {
                $this->m_attributes->Remove($name, null, self::SN_Flash);
                $this->m_attributes->Remove($name, null, self::SN_FlashRemove);
            }
        }

        $attributes = array();
        foreach ($this->m_attributes->GetNamespaces() as $namespace)
        {
            $attributes[$namespace] = $this->m_attributes->GetValues($namespace);
        }

        // write attributes to the storage
        $this->m_storage->Write(self::SN_Attribute, $attributes);
    }
}

?>
